package org.fei.ClassDiagram;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import javax.swing.SwingUtilities;
import org.fei.ClassDiagram.Editor.Wrappers.ClassDataWrapper;
import org.fei.ClassDiagram.Editor.Wrappers.MethodDataWrapper;
import org.fei.ClassDiagram.Editor.Wrappers.VariableDataWrapper;
import org.fei.ClassDiagram.Layout.LayoutService;
import org.fei.ClassDiagram.Layout.Package.PackageHierarchicalLayoutService;
import org.fei.ClassDiagram.WindowComponents.Diagram;
import org.fei.ClassDiagram.WindowComponents.Message;
import org.fei.ClassDiagram.WindowComponents.MessageJPanel;
import org.fei.ClassDiagram.data.*;
import org.fei.ClassDiagram.Element.Class.AbstractClassDrawing;
import org.fei.ClassDiagram.Element.Class.ClassDrawing;
import org.fei.ClassDiagram.Element.Class.Class;
import org.fei.ClassDiagram.Element.Class.EnumClass;
import org.fei.ClassDiagram.Element.Class.InterfaceClass;
import org.fei.ClassDiagram.drawing.Lines.AsotiationArrow;
import org.fei.ClassDiagram.drawing.Lines.InheritanceArrow;
import org.fei.ClassDiagram.drawing.Lines.InnerClassArrow;
import org.fei.ClassDiagram.drawing.Lines.InterfaceArrow;
import org.fei.ClassDiagram.drawing.Lines.Line;
import org.fei.ClassDiagram.Element.Package.Package;
import org.fei.ClassDiagram.Element.Package.PackageService;
import org.netbeans.modules.classfile.ClassFile;
import org.netbeans.modules.classfile.ClassName;
import org.netbeans.modules.classfile.InnerClass;
import org.netbeans.modules.classfile.Method;
import org.netbeans.modules.classfile.Variable;
import org.openide.filesystems.FileObject;

/**
 * Velmi dolezita trieda, ktora skuma jednotlive class subory a vytvara instancie prislusnych "grafickych" tried,
 * tj tried ktore dane informacie vykresluju.
 * 
 * Proces ziskavania informacii je popisany v komentaroch priamo v kode.
 * @author Tomas
 */
public class ClassFileScanner {

    private ArrayList<String> classFiles;
    private String classDirPath;
    
   /*
    * Kolekcia tried ktore znazornuju triedy, rozhrania atd.
    */
    private ArrayList<Class> classes = new ArrayList<Class>();
   /*
    * Kolekcia tried ktore znazornuju vztahy medzi triedami.
    */
    private ArrayList<Line> lines = new ArrayList<Line>();
   /*
    * Kolekcia tried ktore znazornuju baliky.
    */
    private ArrayList<Package> packages;

    public ClassFileScanner(ArrayList<String> classesPath, String classDirPath) {

        this.classFiles = classesPath;
        this.classDirPath = classDirPath;
        String projectName = ProjectScannerFactory.getProjectScanner().getProjectName();
        
        try {      
            //URL classDirUrl = new URL("file:///" + classDirPath + "/");
            //URL[] urls = {classDirUrl};
            
            for (String s : this.classFiles) {
                // postupne sa prejde vsetkymi triedami
                ClassFile classFile = new ClassFile(s);       

                // vytiahnu sa metody
                Collection<Method> methods = classFile.getMethods();
                // vytiahnu sa premenne
                Collection<Variable> variables = classFile.getVariables();

                // triedy ktore reprezentuju "vzhlad" metod, tj spravne vykreslovanie v ramci UML standartu
                ArrayList<MethodLook> mDatas = new ArrayList<MethodLook>();

                
                ClassDataWrapper data = new ClassDataWrapper(classFile);

                // metody triedy
                for (Method m : methods) {
                    if (classFile.isEnum()) {
                        // <init> a <clinit> je oznacenie pre konstuktory... v type enum ich nechceme
                        if (!(m.getName().contains("<init>") || m.getName().contains("<clinit>"))) {
                            MethodDataWrapper tmp = new MethodDataWrapper(m);
                            mDatas.add(new MethodLook(tmp, data));
                        }
                    } else {
                        // ak je staticke a je konstruktor tak sa neprida
                        // fuha tu som si uz neni isty, su to divociny asi aby sa nepridal staticky konstruktor?
                        if (!(m.isStatic() && (m.getName().contains("<init>") || m.getName().contains("<clinit>"))))
                        {
                            if (classFile.getInnerClasses().isEmpty()) {
                                MethodDataWrapper tmp = new MethodDataWrapper(m);
                                mDatas.add(new MethodLook(tmp, data));
                            }
                            else if (m.isStatic() && m.getName().startsWith("access$")) {
                                if (!m.getParameters().isEmpty()) {
                                    if (m.getParameters().get(0).getName().equals("x0") && classFile.getName().toString().endsWith(m.getParameters().get(0).getDeclaration().split(" ")[0])) {
                                        continue; // pristupove metody pre vnorene triedy sa nepridaju
                                    }
                                }
                                else {
                                    MethodDataWrapper tmp = new MethodDataWrapper(m);
                                    mDatas.add(new MethodLook(tmp, data));
                                }
                            }
                            else { 
                                MethodDataWrapper tmp = new MethodDataWrapper(m);
                                mDatas.add(new MethodLook(tmp, data));
                            }
                        }
                    }
                }
                
                // pridanie metod do datovej struktury bez konstruktorov, za ucelom editacie
                ArrayList<MethodDataWrapper> methodsEdit = new ArrayList<MethodDataWrapper>();
                for (MethodLook m : mDatas) {
                    MethodDataWrapper classFileMethod = m.getMethod();
                    
                    if (classFileMethod.getName().contains("<init>") || classFileMethod.getName().contains("<clinit>"))
                        continue;   // konstruktory nechceme
                    
                    methodsEdit.add(classFileMethod);
                }
                data.setMethods(methodsEdit);

                // praca s premennymi, konstanty enum sa pridaju zvlast
                ArrayList<VariableLook> vDatas = new ArrayList<VariableLook>();
                ArrayList<VariableLook> enumConstants = new ArrayList<VariableLook>();
                ArrayList<VariableDataWrapper> varwraplist = new ArrayList<VariableDataWrapper>();
                for (Variable v : variables) {
                    
                    if (classFile.isEnum() && !v.getName().startsWith("$VALUES")) {
                        VariableDataWrapper tmp = new VariableDataWrapper(v);
                        varwraplist.add(tmp);
                        enumConstants.add(new VariableLook(tmp));
                        continue;
                    }
                    // zamedzuje sa pridaniu premennej co zacina na this$0, uz neviem presne co to je, generuje sa to do class suboru
                    if (!v.getName().startsWith("this$0") && !v.getName().startsWith("$")) {
                        VariableDataWrapper tmp = new VariableDataWrapper(v);
                        varwraplist.add(tmp);
                        vDatas.add(new VariableLook(tmp));
                    }
                }        
                
                // pridanie do ClassData pre editovanie                
                data.setVariables(varwraplist);
                
                Class d;
                // vytvorenie spravnych instancii podtried triedy Class
                if (Modifier.isInterface(classFile.getAccess())) {
                    d = new InterfaceClass(0, 0, data, mDatas, vDatas);
                } else if (Modifier.isAbstract(classFile.getAccess())) {
                    d = new AbstractClassDrawing(0, 0, data, mDatas, vDatas);
                } else if (classFile.isEnum()) {
                    d = new EnumClass(0, 0, data, mDatas, enumConstants);
                } else {
                    d = new ClassDrawing(0, 0, data, mDatas, vDatas);
                }
                
                classes.add(d);                
            }
            
            // vytvorenie ciar pre dedicnost
            for (Class child : classes) {
                for (Class parent : classes) {
                    if (!parent.equals(child)) {
                        // dedicnost
                        if (child.getClassData().getClassFile().getSuperClass().equals(parent.getClassData().getClassFile().getName())) {
                            parent.setIsSuperclass(true);
                            lines.add(new InheritanceArrow(child.getTopPoint(), parent.getBottomPoint(), child, parent));
                            parent.addChild(child);
                            child.setParent(parent);
                        }
                        // implementacia rozhrani
                        for (ClassName interfaceClass : child.getClassData().getClassFile().getInterfaces()) {
                            if (interfaceClass.equals(parent.getClassData().getClassFile().getName())) {
                                parent.setIsSuperclass(true);
                                lines.add(new InterfaceArrow(child.getTopPoint(), parent.getBottomPoint(), child, parent));
                                parent.addChild(child);
                                if (child.getParent() == null) child.setParent(parent);   
                            }
                        }
                        // vnorene triedy
                        for (InnerClass innerClass : child.getClassData().getClassFile().getInnerClasses()) {
                            if (innerClass.getName().equals(parent.getClassData().getClassFile().getName())) {
                                child.setHasInnerClasses(true);
                                child.addInnerClass(parent);
                                parent.setIsInncerClassChild(true);
                                lines.add(new InnerClassArrow(parent.getLeftPoint(), child.getInnerClassesPoint(), child, parent));
                            }
                        }
                        // asociacie
                        for (VariableLook vl : child.getVariables()) {
                            //Message.showMessage(v.getDescriptor() + "\n" + v.getName() + "\n" + v.getTypeSignature() + "\n" + v.toString() );
                            VariableDataWrapper v = vl.getVar();
                            String type = v.getDescriptor();
                            if (type.contains("L") && type.contains(";")) {
                                type = type.substring(type.indexOf("L") + 1, type.indexOf(";"));
                            }
                            type = type.replace("/", ".");
                            //Message.showMessage(type);

                            if (type.equals(parent.getClassData().getClassFile().getName().toString()) && !v.getName().startsWith("this$0")) {

                                parent.addAsotiationChild(child);
                                child.addAsotiationClass(parent);
                                v.setAsotioationType(parent.getClassData());
                                boolean isFirst = true;
                                for (Line l : lines) {
                                    if (l instanceof AsotiationArrow) {
                                        if (l.getStart().equals(child.getLeftPoint()) && l.getEnd().equals(parent.getRightPoint())) {
                                            isFirst = false;
                                        }
                                    }
                                }
                                // koli vykreslovaniu v triedach
                                vl.setIsAsotiation(true);
                                lines.add(new AsotiationArrow(child.getClassData(), child.getLeftPoint(), parent.getRightPoint(), v, parent.getNumberOfAsotiation(), isFirst, child, parent));
                            }
                            // takuto premennu maju v sebe vnorene triedy, nechcem tuto premennu vykreslovat
                            if (v.getName().startsWith("this$0")) {
                                vl.setIsAsotiation(true);
                            }
                        }
                    }
                }
            }
        } catch (IOException ex) {         
            Message.showMessage("ClassFileScanner.java: " + ex.getMessage());
        } 

        if (classes.isEmpty()) {
            String[] msg = {"Your project does not contains any classes."};
            new ClassDiagramView(new MessageJPanel(msg), projectName);
            return;
        }
        
        // Vytvorenie graphics
        BufferedImage bi = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
        Graphics graphics = bi.createGraphics();
        
        // Vytvorenie balíkov
        PackageService packageService = new PackageService(classes);
        packages = packageService.createPackages();           
        
        // Layoutovanie
        LayoutService layoutService = new LayoutService(new PackageHierarchicalLayoutService(graphics, 50, 50, 50, 50), 10, 30, new Point(10, 35));
        Dimension diagramDimenstion = layoutService.applyLayout(packages);      
        
        // Diagram tried a balíkov
        final Diagram diagram = new Diagram(classes, lines, packages, diagramDimenstion, packageService);
               
        diagram.paint(graphics);        
        graphics.dispose();
        
        final String name = projectName;
        
        try {
            SwingUtilities.invokeAndWait(new Runnable() {

                @Override
                public void run() {
                    ClassDiagramView view = new ClassDiagramView(diagram, name);
                }
                
            });
        } catch (InterruptedException ex) {
            Message.showMessage("ClassFileScanner.java: " + ex.toString());
        } catch (InvocationTargetException ex) {
            Message.showMessage("ClassFileScanner.java: " + ex.toString());
        }

        
    }
}